/*******************************************************************************
 * Copyright (c) 2006, 2016 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.concurrent;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.core.runtime.Platform;

/**
 * Instrumented base class for
 * <ul>
 * <li>Runnable/Callable objects that are to be submitted to a DsfExecutor
 * <li>objects that have a primary execution method (resembling
 * <code>Runnable.run</code>) and that tend to be exercised from a DSF Executor
 * and/or that submit work to a DSF executor.
 * </ul>
 * 
 * <p>
 * Derivative classes benefit from additional fields that can be of help when
 * debugging a DSF session. Derivatives that implement Runnable/Callable and are
 * fed to DSF executors additionally benefit from tracing (when turned on by the
 * user). A trace message is generated when the Runnable/Callable is submitted
 * to the DsfExecutor.
 * 
 * <p>
 * Note that DSF executors need not be fed instances of this type. It is
 * perfectly fine for clients to call the DSF executor with a plain vanilla
 * Runnable/Callable, but such objects will obviously not benefit from the
 * instrumentation.
 * 
 * <p>
 * When this base class is used to instrument a Runnable/Callable that is
 * destined for a DSF executor, no additional work is imposed on the derived
 * class. In all other cases, the subclass is responsible for calling
 * {@link #setSubmitted()} from its primary execution method (e.g.,
 * {@link RequestMonitor#done()}
 * 
 * All fields and methods in this class are for tracing and debugging purposes
 * only.
 * 
 * @since 1.0
 */
@ThreadSafe
public class DsfExecutable {
    /** 
     * Flag indicating that tracing of the DSF executor is enabled.  It enables
     * storing of the "creator" information as well as tracing of disposed
     * runnables that have not been submitted to the executor.  
     */
    static boolean DEBUG_EXECUTOR = false;

	/**
	 * Flag indicating that monitor objects should be instrumented. A monitor is
	 * an object that is usually constructed as an anonymous inner classes and
	 * is used when making an asynchronous call--one that needs to return some
	 * result or at least notify its caller when it has completed. These objects
	 * usually end up getting chained together at runtime, forming what is
	 * effectively a very disjointed code path. When this trace option is
	 * enabled, these objects are given a String field at construction time that
	 * contains the instantiation backtrace. This turns out to be a fairly
	 * dependable alternative to the standard program stack trace, which is of
	 * virtually no help when debugging asynchronous, monitor-assisted code.
	 */
    static boolean DEBUG_MONITORS = false;

    /** 
     * Flag indicating that assertions are enabled.  It enables storing of the
     * "creator" executable for debugging purposes.
     */
    static boolean ASSERTIONS_ENABLED = false;

    static {
        assert (ASSERTIONS_ENABLED = true) == true;
        DEBUG_EXECUTOR = DsfPlugin.DEBUG && Boolean.parseBoolean(
                Platform.getDebugOption("org.eclipse.cdt.dsf/debug/executor")); //$NON-NLS-1$
        
        DEBUG_MONITORS = DsfPlugin.DEBUG && Boolean.parseBoolean(
                Platform.getDebugOption("org.eclipse.cdt.dsf/debug/monitors")); //$NON-NLS-1$          
    }

	/**
	 * Stack trace indicating where this object was created.
	 */
    final StackTraceWrapper fCreatedAt;

	/**
	 * If this object was created by a runnable/callable that was submitted to a
	 * DsfExecutor, this field holds a reference to its tracing wrapper.
	 * Otherwise, this field is null.
	 */
    final DefaultDsfExecutor.TracingWrapper fCreatedBy;

	/**
	 * If this object is a Runnable/Callable, this flag indicates whether this
	 * object was ever submitted to a DsfExecutor for execution. If this is not
	 * a Runnable/Callable, then this indicates whether the primary execution
	 * method of the object was ever invoked. The subclass is required to
	 * explicitly call this method at the start of that primary method.
	 */
    private volatile boolean fSubmitted = false;
    
    public DsfExecutable() {
        // Use assertion flag (-ea) to jre to avoid affecting performance when not debugging.
        if (ASSERTIONS_ENABLED || DEBUG_EXECUTOR || DEBUG_MONITORS) {
            // Find the runnable/callable that is currently running.
            DefaultDsfExecutor executor = DefaultDsfExecutor.fThreadToExecutorMap.get(Thread.currentThread()); 
            if (executor != null) {
                fCreatedBy = executor.fCurrentlyExecuting;
            } else {
                fCreatedBy = null;
            }
            
            // Get the stack trace and find the first method that is not a 
            // constructor of this object. 
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            Class<?> thisClass = getClass();
            Set<String> classNamesSet = new HashSet<String>();
            while(thisClass != null) {
                classNamesSet.add(thisClass.getName());
                thisClass = thisClass.getSuperclass();
            }
            int i;
            for (i = 3; i < stackTrace.length; i++) {
                if ( !classNamesSet.contains(stackTrace[i].getClassName()) ) break;
            }
            fCreatedAt = new StackTraceWrapper(new StackTraceElement[stackTrace.length - i]); 
            System.arraycopy(stackTrace, i, fCreatedAt.fStackTraceElements, 0, fCreatedAt.fStackTraceElements.length);
        } else {
            fCreatedAt = null;
            fCreatedBy = null;
        }
    }

	/**
	 * Was this object submitted for execution?
	 * 
	 * See {@link #setSubmitted()}
	 */
    public boolean getSubmitted() {
        return fSubmitted;
    }

	/**
	 * Mark that this object was submitted for execution.
	 * 
	 * <p>
	 * More specifically, if this object is a runnable/callable, this method is
	 * called right before the execute/call method is invoked by a DSF executor.
	 * If the object is not a runnable/callable, then this method is called
	 * right before a DsfExecutor invokes a callable/runnable that will invoke
	 * the target method of this object.
	 */
    public void setSubmitted() {
        fSubmitted = true;
    }

	/**
	 * Returns whether this object is always expected to be executed.
	 * We output a trace message if we are garbage collected without having been
	 * executed...that is unless this method returns false. 
	 * 
	 * Subclasses should override this method and return false if instances of
	 * it aren't meant to always be executed, thus avoiding unnecessary trace
	 * output.
	 * 
	 * @return true if this object should always be executed
	 */
    protected boolean isExecutionRequired() {
        return true;
    }

// Bug 306982
//  Disable the use of finalize() method in DSF runnables tracing to avoid 
//  a performance penalty in garbage collection.
//
//	/**
//	 * Checks to see if the object was executed before being garbage collected.
//	 * If not, and it's expected to have been, then output a trace message to
//	 * that effect.
//	 * 
//	 * @see java.lang.Object#finalize()
//	 */
//    @Override
//    protected void finalize() {
//        if (DEBUG_EXECUTOR && !fSubmitted && isExecutionRequired()) {
//            StringBuilder traceBuilder = new StringBuilder();
//
//            // Record the time
//            traceBuilder.append(DsfPlugin.getDebugTime());
//            traceBuilder.append(' ');
//            
//            final String refstr = LoggingUtils.toString(this, false);
//            traceBuilder.append("DSF executable was never executed: ").append(refstr); //$NON-NLS-1$
//            final String tostr = LoggingUtils.trimTrailingNewlines(this.toString());
//            if (!tostr.equals(refstr)) {
//            	traceBuilder.append(" ["); //$NON-NLS-1$
//            	traceBuilder.append(tostr);
//            	traceBuilder.append(']');
//            }
//            traceBuilder.append("\nCreated at:\n"); //$NON-NLS-1$
//            traceBuilder.append(fCreatedAt);
//            
//            DsfPlugin.debug(traceBuilder.toString());
//        }
//    }
}
